#include <object.h>
#include <memory.h>
#include "mem.h"
#include "table.h"

#define TABLE_MAX_LOAD 0.75

void initTable(Table* table) {
    table->count = 0;
    table->capacity = 0;
    table->entries = NULL;
}

void freeTable(Table* table) {
    FREE_ARRAY(Entry, table->entries, table->capacity);
}

static Entry* findEntry(Entry* entries, int capacity, ObjString* key) {
    uint32_t index = key->hash % capacity;
    Entry* tombstone = NULL;
    while (1) {
        Entry* entry = entries + index;
        if (entry->key == NULL) {
            if (IS_NULL(entry->value)) {
                return tombstone != NULL ? tombstone : entry;
            } else {
                if (tombstone == NULL) {
                    tombstone = entry;
                }
            }
        } else if (entry->key == key) {
            return entry;
        }

        index = (index + 1) % capacity;
    }
}

bool getTable(Table* table, ObjString* key, Value* value) {
    Entry* e = NULL;
    if (table->count > 0) {
        e = findEntry(table->entries, table->capacity, key);
    }
    if (e == NULL || e->key == NULL) {
        return false;
    }
    *value = e->value;
    return true;
}

static void adjustCapacity(Table* table, int capacity) {
    Entry* entries = ALLOCATE(Entry, capacity);
    for (int i = 0; i < capacity; i++) {
        Entry* e = entries + i;
        e->key = NULL;
        e->value = NULL_VAL;
    }

    table->count = 0;
    for (int i = 0; i < table->capacity; i++) {
        Entry* entry = table->entries + i;
        if (entry->key == NULL) {
            continue;
        }
        Entry* dest = findEntry(entries, capacity, entries->key);
        dest->key = entry->key;
        dest->value = entry->value;
        table->count++;
    }
    FREE_ARRAY(Entry, table->entries, table->capacity);
    table->entries = entries;
    table->capacity = capacity;
}

bool putTable(Table* table, ObjString* key, Value value) {
    if (table->count + 1 > table->capacity * TABLE_MAX_LOAD) {
        int capacity = GROW_CAPACITY(table->capacity);
        adjustCapacity(table, capacity);
    }
    Entry* entry = findEntry(table->entries, table->capacity, key);
    bool isNewKey = entry->key == NULL;
    if (isNewKey && IS_NULL(entry->value)) {
        table->count++;
    }
    entry->key = key;
    entry->value = value;
    return isNewKey;
}

bool deleteTable(Table* table, ObjString* key) {
    if (table->count == 0) {
        return false;
    }

    Entry* entry = findEntry(table->entries, table->capacity, key);
    if (entry->key == NULL) {
        return false;
    }

    entry->key = NULL;
    entry->value = BOOL_VAL(true);
    return true;
}

void tableAddAll(Table* from, Table* to) {
    for (int i = 0; i < from->capacity; i++) {
        Entry* e = from->entries + i;
        if (e->key != NULL) {
            putTable(to, e->key, e->value);
        }
    }
}

ObjString* tableFindString(Table* table, const char* s, int length, uint32_t hash) {
    if (table->count == 0) {
        return NULL;
    }
    uint32_t index = hash % table->capacity;

    while (1) {
        Entry* e = table->entries + index;
        if (e->key == NULL) {
            if (IS_NULL(e->value)) {
                return NULL;
            }
        } else if (e->key->length == length &&
                   e->key->hash == hash &&
                   memcmp(e->key->str, s, (size_t) length) == 0) {
            return e->key;
        }
        index = (index + 1) % table->capacity;
    }
}

void markTable(Table *table) {
    for (int i = 0; i < table->capacity; i++) {
        Entry *entry = table->entries + i;
        markObject((Obj *) entry->key);
        markValue(entry->value);
    }
}

void tableRemoveWhite(Table *table) {
    for (int i = 0; i < table->capacity; ++i) {
        Entry *entry = table->entries + i;
        if (entry->key != NULL && !entry->key->obj.isMarked) {
            deleteTable(table, entry->key);
        }
    }
}
